#include "src/emule/known_file.h"
#include <assert.h>
#include "src/error_code.h"
#include "src/io/file.h"
#include "src/emule/aich_hash_tree.h"
#include "src/emule/emule_define.h"
#include "src/emule/aich_hash_sha1.h"

jellyfish::emule::known_file::known_file()
:file_(NULL)
,tree_(NULL)
{
    
}

jellyfish::emule::known_file::~known_file()
{
    if (NULL!=file_)
    {
        delete file_;
        file_ = NULL;
    }

    if (NULL!=tree_)
    {
        delete tree_;
        tree_ = NULL;
    }
}

int32 jellyfish::emule::known_file::create_from_file( const char* file_name )
{
    assert(NULL!=file_name);
    assert(NULL==file_);
    assert(NULL==tree_);

    file_ = new io::file;
    if (!file_->open(file_name,"rb"))
    {
        delete file_;
        file_ = NULL;
        return file_not_valid;
    }

    uint64 file_length = file_->get_length();
    tree_ = new aich_hash_tree();
    tree_->set_size(file_length);

    // create hash set
    uint64 togo = file_length;
    uint32 hashcount = 0;
    for (hashcount = 0; togo >= PARTSIZE; )
    {
        aich_hash_tree* pBlockAICHHashTree = tree_->find_hash((uint64)hashcount * PARTSIZE, PARTSIZE);
        assert( pBlockAICHHashTree != NULL );

        uint8* newhash = new uint8[16];
        if (!create_hash(file_, PARTSIZE, newhash, pBlockAICHHashTree))
        {
            delete[] newhash;
            newhash = NULL;
            return false;
        }

    }

     return file_not_valid;
}

bool jellyfish::emule::known_file::create_hash( io::file* fp, uint64 uSize, uint8* pucHash, aich_hash_tree* pShaHashOut )
{
    assert(NULL!=fp && uSize>0);
    assert(NULL!=pucHash);
    assert(NULL!=pShaHashOut);

    uint64  Required = uSize;
    uint8   X[64*128] = {0};
    uint64	posCurrentEMBlock = 0;
    uint64	nIACHPos = 0;
    aich_hash_algorithm* hash_algorithm = new aich_hash_sha1;


    while (Required >= 64)
    {
        uint32 len;
        if ((Required / 64) > sizeof(X)/(64 * sizeof(X[0])))
        {
            len = sizeof(X)/(64 * sizeof(X[0]));
        }
        else
        {
            len = (uint32)Required / 64;
        }

        fp->read(&X, len*64);
        // SHA hash needs 180KB blocks
        if (pShaHashOut != NULL)
        {
            if (nIACHPos + len*64 >= EMBLOCKSIZE)
            {
                uint32 nToComplete = (uint32)(EMBLOCKSIZE - nIACHPos);
                hash_algorithm->add(X, nToComplete);
                assert( nIACHPos + nToComplete == EMBLOCKSIZE );
                pShaHashOut->SetBlockHash(EMBLOCKSIZE, posCurrentEMBlock, hash_algorithm);
                posCurrentEMBlock += EMBLOCKSIZE;
                hash_algorithm->reset();
                hash_algorithm->add(X+nToComplete,(len*64) - nToComplete);
                nIACHPos = (len*64) - nToComplete;
            }
            else
            {
                hash_algorithm->add(X, len*64);
                nIACHPos += len*64;
            }
        }

        //if (pMd4HashOut != NULL)
        //{
        //    md4.Add(X, len*64);
        //}

        Required -= len*64;
    }

    Required = uSize % 64;
    if (Required != 0)
    {
        fp->read(&X, (uint32)Required);

        if (pShaHashOut != NULL)
        {
            if (nIACHPos + Required >= EMBLOCKSIZE)
            {
                uint32 nToComplete = (uint32)(EMBLOCKSIZE - nIACHPos);
                hash_algorithm->add(X, nToComplete);
                assert( nIACHPos + nToComplete == EMBLOCKSIZE );
                pShaHashOut->SetBlockHash(EMBLOCKSIZE, posCurrentEMBlock, hash_algorithm);
                posCurrentEMBlock += EMBLOCKSIZE;
                hash_algorithm->reset();
                hash_algorithm->add(X+nToComplete, (uint32)(Required - nToComplete));
                nIACHPos = Required - nToComplete;
            }
            else
            {
                hash_algorithm->add(X, (uint32)Required);
                nIACHPos += Required;
            }
        }
    }
    if (pShaHashOut != NULL)
    {
        if (nIACHPos > 0)
        {
            pShaHashOut->SetBlockHash(nIACHPos, posCurrentEMBlock, hash_algorithm);
            posCurrentEMBlock += nIACHPos;
        }
        assert( posCurrentEMBlock == uSize );
        //VERIFY( pShaHashOut->ReCalculateHash(hash_algorithm, false) );
    }

    //if (pMd4HashOut != NULL)
    //{
    //    md4.Add(X, (uint32)Required);
    //    md4.Finish();
    //    md4cpy(pMd4HashOut, md4.GetHash());
    //}

    delete hash_algorithm;


    return false;
}
